#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Croquette v1.0
# By Michael Lee and David Gomes
# Released August 1st, 2011
#
# Please see /readme_Croquette.txt
# Licensed under the MIT License (see /license_Croquette.txt)
#
# Written in Python 2.7
#

import os, os.path
import argparse

import meta
import ticonverter


class CommandIO():
    def __init__(self):
        """Creates the command line parser."""
        self._create_parser()
        self._options = {}
        return

    def _create_parser(self):
        """Adds options for the parser."""
        ## Creating the parser
        self._parser = argparse.ArgumentParser(description=meta.description,
                                               add_help=True,
                                               prog=meta.program_name)
        ## Positional (mandatory) arguments
        self._parser.add_argument("input_path",
                                  nargs='?',
                                  type=str,
                                  help="The path to the file to compile or " \
                                       "decompile.")
        ## Optional arguments
        self._parser.add_argument("-v", "--version",
                                  action="version",
                                  version=meta.version,
                                  help="Display the version and release date.")
        self._parser.add_argument("-c", "--colons", "--colon",
                                  action="store_const",
                                  const=True,
                                  default=False,
                                  help="Adds colons to the start of each line.",
                                  dest="colons")
        self._parser.add_argument("-f", "--force",
                                  action="store",
                                  nargs='?',
                                  const=None,
                                  default=None,
                                  type=str,
                                  choices=["axe", "basic"],
                                  help="Forces an .8xp or txt file to convert to " \
                                       "a specific format.  Accepted arguments:" \
                                       " axe, basic.",
                                  dest="force")
        self._parser.add_argument("-n", "--name",
                                  action="store",
                                  nargs="?",
                                  const=None,
                                  default=None,
                                  help="The name you want your 8xp to have.",
                                  dest="program_name")
        self._parser.add_argument("-o", "--only",
                                  action="store",
                                  nargs="?",
                                  const=None,
                                  default=None,
                                  choices=["8xp", "txt"],
                                  help="Converts only programs with the given " \
                                       "extention.  Accepted arguments: 8xp, "\
                                       "txt.",
                                  dest="only")
        self._parser.add_argument("-t", "--tabs", "--tab",
                                  action="store",
                                  nargs="?",
                                  const=4,
                                  default=0,
                                  type=int,
                                  help="The following number will be the " \
                                       "number of spaces per each indent.  " \
                                       "Defaults to 4 spaces.",
                                  dest="tab_length")
        self._parser.add_argument("-s", "--special",
                                  action="store_const",
                                  const="special",
                                  default="ascii",
                                  help="If present, makes any .txt output" \
                                       "use special characters (-> turns to "\
                                       "→, for example).",
                                  dest="encoding")
        return

    def parse(self):
        """Runs the command line parser.

        Arguments:
         - None

        Returns:
         - A dictionary of name-key pairs:
           {"colons"       : bool,
            "tab_length"   : int,
            "input_path"   : str,
            "output_path"  : str,
            "program_name" : str or None}
        """
        options = self._parser.parse_args()

        if not(options.input_path):
            self._parser.parse_args(["--help"])
        
        self._options["colons"] = options.colons
        self._options["tab_length"] = options.tab_length

        self._options["force"] = options.force
        self._options["only"] = options.only

        self._options["program_name"] = options.program_name
        self._options["encoding"] = options.encoding

        self._options["input_paths"] = self._list_input_paths(options.input_path)
        self._options["output_paths"] = self._make_output_name(
                                                self._options["input_paths"])
        
        return self._options

    def _list_input_paths(self, input_path):
        """Takes a path, and (if it's a directory), returns all subfiles.

        NOTE:
        If a specific file type is forced, only files matching it will be
        returned.
        
        Arguments:
         - input_path: The path to either a file or a directory

        Returns:
         - A list of paths to files.
        """
        if self._options["only"] == "8xp":
            # If only .8xp files will be converted...
            scraper = DirectoryScraper(["8xp", "8XP"])
        elif self._options["only"] == "txt":
            # If I'm forcing the 8xp to turn into a txt (or type of txt)...
            scraper = DirectoryScraper(["txt", "TXT"])
        else:
            # If I'm not forcing anything...
            scraper = DirectoryScraper(["txt", "TXT", "8xp", "8XP"])
        return scraper.scrape(input_path, recursive=True)
        # Hmm, maybe it shouldn't be recursive?
                                    

    def _make_output_name(self, input_paths):
        """Switches the extentions"""

        output = []
        for path in input_paths:
            mark = path.rfind(os.extsep)+1    # Just in case 
            extention = path[mark:]
            if extention == "8xp":
                output.append(path[:mark] + "txt")
            elif extention == "txt":
                output.append(path[:mark] + "8xp")
        return output



class DirectoryScraper():
    def __init__(self, extentions):
        """Sets the scraper ready.

        Arguments:
        - extentions: A list containing strings of valid extentions.
                      Either:
                      d = DirectoryScraper(['html', 'css'])
                      or:
                      d = DirectoryScraper(['.html', '.css'])
                      are acceptable.

        Returns:
        - Nothing
                     """
        
        self.set_extentions(extentions)
        return

    def scrape(self, input_=os.getcwd(), recursive=True):
        """Returns a list of all files that end in the extentions given.

        Arguments:
         - input_:    A string (or list of strings) of directorie(s) or file(s)
                      to check.  If left blank, it will check the current
                      directory by default.
         - recursive: Optional.  If True, and 'input_' is blank or points
                      to a directory, all files in the directory and all its
                      subdirectories will be checked.  If False, it won't
                      check anything in the subdirectories.

        Returns:
         - A list containing filenames (absolute)"""
        check = True
        stack = []
        if type(input_) == list:
            stack.extend(input_)
        elif type(input_) == str:
            stack.append(input_)
        else:
            raise TypeError("input_ must be a string or a list of strings.")
        # The stack contains items that have not been checked to be valid.

        valid_files = []
        while stack:
            item = stack.pop()
            if os.path.isdir(item):
                os.chdir(item)
                if check:
                    # Since the item was a folder, add all items to the stack.
                    for thing in os.listdir(item):
                        stack.append(os.path.abspath(thing))
                        check = recursive   # So a folder is checked only once.
            else:
                # Error occurs when item is not a folder
                # and is therefore a file.
                if self._does_file_exist(item):
                    valid_files.append(item)

        return valid_files

    def _does_file_exist(self, filename):
        """Checks if the file is valid and ends in the right extention.

        Arguments:
         - filename: The filename you want to check.

        Returns:
         - A bool."""
        if not(os.path.exists(filename)):
            return False
        
        extention = filename[filename.rfind(os.extsep)+1:]
        if not(extention in self._extentions):
            return False
        return True

    def set_extentions(self, extentions):
        """Changes the list of valid extentions.

        Arguments:
         - extentions: A list containing strings of valid extentions.
                       Either:
                       d = DirectoryScraper(['html', 'css'])
                       or:
                       d = DirectoryScraper(['.html', '.css'])
                       are acceptable.

        Returns:
         - Nothing."""
        if type(extentions) != list:
            raise TypeError("Must be a list of strings")
        self._extentions = []
        for item in extentions:
            if type(item) != str:
                raise TypeError("Must be a list of strings")
            self._extentions.append(item.replace((os.extsep), ""))
        return


def start():
    """Reads input from the command line, and converts the files."""
    reader = CommandIO()
    options = reader.parse()
    
    parser = ticonverter.Parser(colons=options["colons"],
                                tab_length=options["tab_length"],
                                encoding=options["encoding"],
                                detokenize_type=options["force"])

    for i, input_path in enumerate(options["input_paths"]):
        print input_path
        parser.just_do_it(input_path,
                          options["output_paths"][i],
                          options["program_name"])
        # encoding="ascii" for normal chars,
        # encoding="special for special chars.
    print "\nSuccess!\n"
    return


if __name__ == "__main__":
    start()
